package com.jadobo.xswing.xhtml.parser.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.jadobo.xswing.attrib.HtmlElementTag;
import com.jadobo.xswing.model.Body;
import com.jadobo.xswing.model.Form;
import com.jadobo.xswing.model.Heading;
import com.jadobo.xswing.model.IElement;
import com.jadobo.xswing.model.Input;
import com.jadobo.xswing.model.Input.InputType;
import com.jadobo.xswing.model.Page;
import com.jadobo.xswing.util.Conv;
import com.jadobo.xswing.util.Doc;
import com.jadobo.xswing.util.NodeListAdapter;
import com.jadobo.xswing.util.Str;
import com.jadobo.xswing.xhtml.parser.ITransform;

public class DOMHtml implements ITransform {
	
	private static final Logger LOG = Logger.getLogger(DOMHtml.class);
	private static AtomicInteger IDFACTORY = new AtomicInteger(1);

	private Document document;
	
	public DOMHtml(InputStream source) 
		throws ParserConfigurationException, SAXException, IOException {
		
			DocumentBuilder builder = initDOM();
			document = builder.parse(source);
			
		
	}


	public DOMHtml(File file) 
		throws ParserConfigurationException, SAXException, IOException {

		DocumentBuilder builder = initDOM();
		document = builder.parse(file);
	}


	private DocumentBuilder initDOM() throws ParserConfigurationException {
		
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		return factory.newDocumentBuilder();
		
	}
	
	@Override
	public Page getPage() {
		
		Page page = new Page();
		
		page.setTitle(getTitle(document));
		
		page.setBody(getBody(document));
		
		return page;
	}


	private String getTitle(Document doc) {
		
		NodeList heads = doc.getElementsByTagName("head");
		
		Iterable<Node> iterator = new NodeListAdapter(heads);
		
		for(Node head : iterator) {
			if("html".equals(Doc.getParentElementName(head))) {
				Node title = Doc.findChildElement(head, "title");
				if(title != null) {
					// get element value
					return title.getTextContent();
				}
			}
		}
		
		return null;
	}


	private Body getBody(Document doc) {
		
		Body body = null;

		NodeList bodies = doc.getElementsByTagName("body");

		Iterable<Node> iterator = new NodeListAdapter(bodies);
		
		for(Node bodyNode : iterator) {
			if("html".equals(Doc.getParentElementName(bodyNode))) {

				body = new Body();
				constructBody(body, bodyNode);
				
				break;
			}
		}
				
		return body;
	}


	private void constructBody(Body body, Node bodyNode) {
		
		construct(body.getElements(), bodyNode, null);
		
	}


	private void construct(List<IElement> elements, Node parentNode, IElement parentElement) {

		Iterable<Node> nodes = new NodeListAdapter(parentNode.getChildNodes());

		for(Node node : nodes) {
			
			HtmlElementTag tag = Conv.elementTag(node);
			
			evaluateTag(tag, node, elements, parentElement);
			
		}
		
	}


	private void evaluateTag(HtmlElementTag tag, Node node,
			List<IElement> placeholder, IElement parentElement) {
		
		switch(tag) {
		
		case UNKNOWN_OR_UNHANDLED:
			break;
		
		case h1:
		case h2:
		case h3:
		case h4:
		case h5:
		case h6: 
			
			placeholder.add(new Heading(parentElement, tag, node.getTextContent()));
			
			break;
			
		case p:
			break;
		case div:
			break;
		case label:
			break;
		case input:
			
			Input input = createInput(node, parentElement);
			
			if(input != null) {
				placeholder.add(input);
			}
			break;
			
		case form:
			
			Form form = createForm(node, parentElement);
			if(form != null) {
				placeholder.add(form);
				
				processChildren(node, form);
			}
			break;
		
		default:
			throw new IllegalArgumentException("Please add implementation for tag: " + tag);
		
		}
		
	}


	private void processChildren(Node parentNode, IElement parentElement) {
		
		// pre-condition check
		if(parentNode == null || parentElement == null) {
			return;
		}
		
		HtmlElementTag tag = parentElement.getHtmlElementTag();
		if(!tag.isComposite()) {
			return;
		}
		
		
		Iterable<Node> list = new NodeListAdapter(parentNode.getChildNodes());
		
		for(Node node : list) {
			HtmlElementTag nodeTag = Conv.elementTag(node);
			
			if(tag.isAllowed(nodeTag)) {
				// recursive call...
				construct(parentElement.getElements(), parentNode, parentElement);
			}
			
		}
		
	}


	private Form createForm(Node node, IElement parentElement) {
		
		// pre-condition check
		if(node == null) {
			return null;
		}
		
		String id = null;
		String action = null;
		
		Node idNode = node.getAttributes().getNamedItem("id");
		if(idNode != null) {
			id = idNode.getNodeValue();
		}
		if(Str.isNullOrEmpty(id)) {
			id = "id" + IDFACTORY.getAndIncrement();
		}
			
		Node actionNode = node.getAttributes().getNamedItem("action");
		if(actionNode != null) {
			action = actionNode.getNodeValue();
		}
		
		return new Form(parentElement, id, action);
	}


	private Input createInput(Node node, IElement parentElement) {

		
		Node typeNode = node.getAttributes().getNamedItem("type");
		if(typeNode == null) {
			LOG.error("type attribute is mandatory for input element");
			return null;
		}

		InputType inputType = null;
		try {
			inputType = InputType.valueOf(typeNode.getNodeValue());
		} catch (Exception e) {
			LOG.error("Invalid input type: " + typeNode.getNodeValue(), e);
			return null;
		}
		
		String name = null;
		Node nameNode = node.getAttributes().getNamedItem("name");
		if(nameNode != null) {
			name = nameNode.getNodeValue();
		}
		if(name == null) {
			// auto generate
			name = "name" + IDFACTORY.getAndIncrement();
		}

		String value = null;
		Node valueNode = node.getAttributes().getNamedItem("value");
		if(valueNode != null) {
			value = valueNode.getNodeValue();
		}
		
		Input input = new Input(parentElement, inputType, name , value);
		
		
		
		return input;
	}
	

}
